//
//  STDSPuzzleCartoonEffect.h
//  STDSRenderSDKIOS
//
//  Created by 陈学明 on 2023/4/27.
//

#ifndef STDSPuzzleCartoonEffect_h
#define STDSPuzzleCartoonEffect_h

#include <stdio.h>

#include "STDSBaseEffect.hpp"
#include <glm/glm.hpp>
#include "STDSBilateralBlurEffect.h"

using namespace glm;

class STDSPuzzleCartoonEffect: public STDSBaseEffect {
public:
    STDSPuzzleCartoonEffect() {
        mEffectType = STDSEffectTypePuzzleCartoon;
        mEdgeBlackLevel = pow((0.0 + 10.0)/20.0, 0.45)*0.1 + 0.916;
        buildToonParams();
    }
    ~STDSPuzzleCartoonEffect() {
//        printf("dealloc--STDSMosaicEffect\n");
    }
    
    void initGL() override {
        STDSBaseEffect::initGL();
    }
    
    void setFloatArguments(const char *name, float *floatValue, int n) override {
        if (strcmp("rects", name) == 0) {
            mRects.clear();
            for (int i = 0; i < n/4; i++) {
                vec4 rect;
                rect.x = floatValue[4*i]; // x
                rect.y = floatValue[4*i+1]; // y
                rect.z = floatValue[4*i+2]; // w
                rect.w = floatValue[4*i+3]; // h
                mRects.push_back(rect);
            }
        } else if (strcmp("mix", name) == 0) {
            mMix = *floatValue;
        }
    }
    
public:
    shared_ptr<STDSShaderProgram> getEffectProgram() {
        if (mPuzzleProgram == nullptr) {
            mPuzzleProgram = mContext->getProgram(kShaderSRCPuzzleStarsMask);
        }
        return mPuzzleProgram;
    }
    
    shared_ptr<STDSShaderProgram> getCartoonProgram() {
        if (mCartoonProgram == nullptr) {
            mCartoonProgram = mContext->getProgram(kShaderSRCCartoon);
        }
        return mCartoonProgram;
    }
    
    shared_ptr<STDSShaderProgram> getBlendProgram() {
        if (mBlendProgram == nullptr) {
            mBlendProgram = mContext->getProgram(kShaderSRCPuzzleCartoon);
        }
        return mBlendProgram;
    }
    
    shared_ptr<STDSBilateralBlurEffect> getBlurEffect() {
        if (mBlurProgram == nullptr) {
            mBlurProgram = make_shared<STDSBilateralBlurEffect>();
            mBlurProgram->setContext(mContext);
        }
        return mBlurProgram;
    }
    
    void draw() override {
        auto program = getEffectProgram();
        auto cartoonProgram = getCartoonProgram();
        auto blendProgram = getBlendProgram();
        if (program == nullptr || cartoonProgram == nullptr || blendProgram == nullptr) {
            return;
        }
        if (mTexture == nullptr) {
            return;
        }
        auto maskfbo = make_shared<STDSRenderTarget>(mSizeSRC.x, mSizeSRC.y);
        maskfbo->bindSelf();
        drawMask(program);
        maskfbo->restore();
        
        auto effect = getBlurEffect();
        auto fbo = make_shared<STDSRenderTarget>(mSizeSRC.x, mSizeSRC.y);
        fbo->bindSelf();
        effect->mSizeSRC = mSizeSRC;
        effect->setTexture(mTexture);
        effect->draw();
        fbo->restore();
        
        auto cartoonFbo = make_shared<STDSRenderTarget>(mSizeSRC.x, mSizeSRC.y);
        cartoonFbo->bindSelf();
        drawCartoon(cartoonProgram, fbo->getTexture());
        cartoonFbo->restore();
        
        drawBlend(blendProgram, mTexture, cartoonFbo->getTexture(), maskfbo->getTexture());
    }
    
    void drawBlend(shared_ptr<STDSShaderProgram> program, shared_ptr<STDSTexture> srcTex, shared_ptr<STDSTexture> cartoonTex, shared_ptr<STDSTexture> maskTex) {
        program->use();
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, srcTex->getId());
        program->setUniform(program->getUniform("uSrcTexture"), 0);
        
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, maskTex->getId());
        program->setUniform(program->getUniform("uMaskTexture"), 1);
        
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, cartoonTex->getId());
        program->setUniform(program->getUniform("uCartoonTexture"), 2);
        
        mAtbRect.setPosVex(program->getAttrib("aPos"));
        mAtbRect.setPosTex(program->getAttrib("aTexCoord"));
        mAtbRect.draw();
        program->unuse();
    }
    
    void drawCartoon(shared_ptr<STDSShaderProgram> program, shared_ptr<STDSTexture> texture) {
        program->use();
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture->getId());
        program->setUniform(program->getUniform("uTexture"), 0);
        program->setUniform(program->getUniform("uEdgeBlackLevel"), mEdgeBlackLevel);
        program->setUniform4fv(program->getUniform("uParams"), 20, mParams.data());
        program->setUniform2f(program->getUniform("uSize"), mSizeSRC.x, mSizeSRC.y);
        program->setUniform2f(program->getUniform("uNorm"), mNorm.x, mNorm.y);
        program->setUniform2f(program->getUniform("uFrag"), float(1.0/mSizeSRC.x), float(1.0/mSizeSRC.y));
        mAtbRect.setPosVex(program->getAttrib("aPos"));
        mAtbRect.setPosTex(program->getAttrib("aTexCoord"));
        mAtbRect.draw();
        program->unuse();
    }
    
    void buildToonParams() {
        mParams.clear();
        int halfWidth = int(ceil(2.0 * 0.5));
        float twoSigmaESquared = 2.0;
        float twoSigmaRSquared = 2.0 * 0.5 * 0.5;
        int two_half_one = 2 * halfWidth + 1;
        int countMax = two_half_one * two_half_one + 1;
        mNorm.x = 0.0;
        mNorm.y = 0.0;
        for (int cnt = 0; cnt < 20; ++cnt) {
            int i = cnt / (two_half_one) - halfWidth;
            int j = cnt - halfWidth - cnt / two_half_one * two_half_one;
            float dx = sqrt(float(i * i + j * j));
            float x = float(i)+0.0001;
            float y = float(j)+0.0001;
            float z = exp(-dx * dx / twoSigmaESquared);
            float w = exp(-dx * dx / twoSigmaRSquared);
            mParams.push_back(x);
            mParams.push_back(y);
            mParams.push_back(z);
            mParams.push_back(w);
            mNorm.x += 2.0 * z;
            mNorm.y += 2.0 * w;
        }
    }
    
    void drawMask(shared_ptr<STDSShaderProgram> program) {
        program->use();
        for (auto rect : mRects) {
            float insetsH = (mMix - 1.0)/2.0;
            float insetsV = (mMix - 1.0)/2.0;
            auto canvaseSize = mContext->getSizeSRC();
            vec4 insetEdge(insetsH, insetsV, insetsH, insetsV);// left top right bottom
            // 靠近左边界
            if (rect.x < 0.5) {
                insetEdge.x = 0;
            }
            // 靠近上边界
            if (rect.y < 0.5) {
                insetEdge.y = 0;
            }
            // 靠近右边界
            if ((canvaseSize.x - rect.x - rect.z) < 0.5) {
                insetEdge.z = 0;
            }
            // 靠近下边界
            if ((canvaseSize.y - rect.y - rect.w) < 0.5) {
                insetEdge.w = 0;
            }
            float scaleX = rect.z/canvaseSize.x;
            float scaleY = rect.w/canvaseSize.y;
            vec2 center(2.0 * (rect.x + rect.z/2.0)/canvaseSize.x - 1.0, 1.0 - 2.0 * (rect.y + rect.w/2.0)/canvaseSize.y);
            auto transMat = glm::translate(glm::mat4(1.0f), vec3(center.x, center.y, 0.0));
            transMat = glm::scale(transMat, vec3(scaleX, scaleY, 1.0));
            
            mPuzzleProgram->setMatrix(mPuzzleProgram->getUniform("uMatVex"), glm::value_ptr(transMat));
            mPuzzleProgram->setMatrix(mPuzzleProgram->getUniform("uMatTex"), glm::value_ptr(mMatT));
            program->setUniform4f(program->getUniform("insetsEdge"), insetEdge.x, insetEdge.y, insetEdge.z, insetEdge.w);

            mAtbRect.setPosVex(program->getAttrib("aPos"));
            mAtbRect.setPosTex(program->getAttrib("aTexCoord"));
            mAtbRect.draw();
        }
        program->unuse();
    }

private:
    vector<vec4> mRects;
    float mMix{1.0};
    int mType{1}; //
    float mEdgeBlackLevel{0.0};//
    shared_ptr<STDSShaderProgram> mPuzzleProgram{nullptr};
    shared_ptr<STDSShaderProgram> mCartoonProgram{nullptr};
    shared_ptr<STDSShaderProgram> mBlendProgram{nullptr};
    shared_ptr<STDSBilateralBlurEffect> mBlurProgram{nullptr};
    
    vector<float> mParams;
    vec2 mNorm{0.0, 0.0};
};
#endif /* STDSPuzzleCartoonEffect_h */
